<?php

namespace app\index\controller;

use InvalidArgumentException;
use stdClass;
use think\Controller;
use think\Db;
use think\Log;
use think\Request;
use Exception;

class Transfer extends Controller
{
    private $appid = '';
    private $mch_id = '';
    private $serial_no = '';
    private $notify_url = 'https://clock.lizaonet.com/index/transfer/notify';
    private $pub_key_id = '';
    private $private_key_path = ROOT_PATH . 'certs/apiclient_key.pem';

    public function __construct()
    {
        $this->appid = C('XCX_APPID');
        $this->mch_id = C('MCH_ID');
        $this->serial_no = C('SERIAL_NO');
        $this->pub_key_id = C('PUB_KEY_ID');
    }

    /**
     * 发起商家转账
     */
    public function send($openid, $total_amount)
    {
        if (empty($openid) || empty($total_amount)) return false;

        $transfer_remark = '会员奖励';
        $transfer_scene_report_infos = [
            [
                "info_type" => "活动名称",
                "info_content" => '新会员有礼',

            ],
            [
                "info_type" => "奖励说明",
                "info_content" => '会员打卡成功奖励',

            ]
        ];

        $url = 'https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills';
        $url_arr = parse_url($url);
        $appid = $this->appid;

        $data = [];
        $time = time();
        $data['appid'] = $appid;
        $data['openid'] = $openid;                                      //批次名称
        $data['out_bill_no'] = 'order' . uniqid();                      //订单编号
        $data['notify_url'] = $this->notify_url;                        //回调接口 需根据自己的情况修改
        $data['transfer_amount'] = intval($total_amount);               //金额 单位 分
        $data['transfer_remark'] = $transfer_remark;                    //备注
        $data['transfer_scene_report_infos'] = $transfer_scene_report_infos;
        $data['transfer_scene_id'] = '1000'; //场景ID

        $header = $this->getHeader($data, $url_arr, $time);
        $data = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $res = $this->curl_post_https($url, $data, $header);
        $res = json_decode($res, true);
        if ($res['state'] === 'WAIT_USER_CONFIRM') {
            return $res;
        } else {
            return false;
        }
    }

    public function cancel($out_bill_no)
    {
        if (empty($out_bill_no)) return false;

        $url = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{$out_bill_no}/cancel";
        $url_arr = parse_url($url);

        $header = $this->getHeader([], $url_arr, time());

        $res = $this->curl_post_https($url, '', $header);
        $res = json_decode($res, true);
        if ($res['state'] === 'CANCELING' || $res['state'] === 'CANCELLED') {
            return true;
        } else {
            return false;
        }
    }

    public function test()
    {
                $this->send('o8LJM14EAbX756gE4-IxcFjvyRrY', 11);
//        $this->cancel('order68f9e642a018d');
    }

    public function notify()
    {
        $data = file_get_contents('php://input');

        $obj = new AesUtil('D7DF392B78D8D788343F419294CA5E94');
        // 获取三个字段
        $data = json_decode($data, true);
        $associatedData = $data['resource']['associated_data'];
        $nonceStr = $data['resource']['nonce'];
        $ciphertext = $data['resource']['ciphertext'];
        $data = $obj->decryptToString($associatedData, $nonceStr, $ciphertext);

        $data = json_decode($data, true);

        $transfer_bill_no = $data['transfer_bill_no'];
        if ($data['state'] === 'SUCCESS') {
            // 转账成功
            Db::name('user_clock')->where(['transfer_bill_no' => $transfer_bill_no])->update(['pay_status' => 2, 'is_pay' => 1, 'update_time' => time()]);
        } else if($data['state'] === 'FAIL') {
            // 转账失败
            $res = Db::name('user_clock')->where(['transfer_bill_no' => $transfer_bill_no])->update(['pay_status' => -1, 'is_pay' => 0, 'update_time' => time()]);
            if ($res) $this->cancel($data['out_bill_no']);
        }

        //        file_put_contents(RUNTIME_PATH . 'notify.txt', json_encode($data, JSON_UNESCAPED_UNICODE) . PHP_EOL, FILE_APPEND);
    }

    public function getHeader($data, $url_arr, $time)
    {
        $nonceStr = generateNonceStr();
        $key = getBillSign($data, $url_arr['path'], $nonceStr, $time, ROOT_PATH . 'certs/apiclient_key.pem');
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"', $this->mch_id, $this->serial_no, $nonceStr, $time, $key);
        $header = [
            'Content-Type:' . 'application/json; charset=UTF-8',
            'Accept:application/json',
            'User-Agent:*/*',
            'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $token,
            'Wechatpay-Serial:' . $this->pub_key_id
        ];

        return $header;
    }

    /**
     * 随机字符串
     **/
    public function generateNonceStr($length = 32)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[random_int(0, strlen($chars) - 1)];
        }
        return $str;
    }

    public function getSign($data = [], $url, $randStr, $time, $key_path)
    {
        $str = "POST" . "\n" . $url . "\n" . $time . "\n" . $randStr . "\n" . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
        $key = file_get_contents($key_path); // 确保秘钥文件是正确的
        $str = $this->getSha256WithRSA($str, $key);
        return $str;
    }

    public function getSha256WithRSA($content, $privateKey)
    {
        $binary_signature = "";
        $algo = "SHA256";
        openssl_sign($content, $binary_signature, $privateKey, $algo);
        $sign = base64_encode($binary_signature);
        return $sign;
    }

    /* PHP CURL HTTPS POST */
    public function curl_post_https($url, $data, $header)
    {
        $action = curl_init();
        curl_setopt($action, CURLOPT_URL, $url);
        curl_setopt($action, CURLOPT_CONNECTTIMEOUT, 60);
        curl_setopt($action, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($action, CURLOPT_HEADER, 0);
        curl_setopt($action, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($action, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($action, CURLOPT_POST, 1);
        curl_setopt($action, CURLOPT_POSTFIELDS, $data);
        curl_setopt($action, CURLOPT_HTTPHEADER, $header);
        $result = curl_exec($action);
        curl_close($action);
        return $result;
    }
}

class AesUtil
{
    /**
     * AES key
     * @var string
     */
    private $aesKey;
    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;

    /**
     * Constructor
     */
    public function __construct($aesKey)
    {
        if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
            throw new InvalidArgumentException('无效的ApiV3Key，长度应为32个字节');
        }
        $this->aesKey = $aesKey;
    }

    /**
     * Decrypt AEAD_AES_256_GCM ciphertext
     * @param string $associatedData AES GCM additional authentication data
     * @param string $nonceStr       AES GCM nonce
     * @param string $ciphertext     AES GCM cipher text
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public
    function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }
        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
            return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
        }
        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
            return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
        }
        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
            return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
                                    $authTag, $associatedData);
        }
        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
}
